/**
 * Copyright (C) 2011 Shih-Yuan Lee (FourDollars) <fourdollars@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Plurk {
    public class Client : Object {
        private static const bool dump = false;

        private static const string request_token_url = "http://www.plurk.com/OAuth/request_token";
        private static const string oauth_token_url   = "http://www.plurk.com/OAuth/authorize?oauth_token=";
        private static const string access_token_url  = "http://www.plurk.com/OAuth/access_token";
        private static const string api               = "http://www.plurk.com/APP/";

        private string consumer_key       = null;
        private string consumer_secret    = null;
        private string oauth_token        = null;
        private string oauth_token_secret = null;

        private OAuth.Client oauth = null;

        public Client(
            string  consumer_key,
            string  consumer_secret,
            string? oauth_token        = null,
            string? oauth_token_secret = null) {
            this.consumer_key       = consumer_key;
            this.consumer_secret    = consumer_secret;
            this.oauth_token        = oauth_token;
            this.oauth_token_secret = oauth_token_secret;
            oauth = new OAuth.Client(consumer_key, consumer_secret, oauth_token, oauth_token_secret);
        }

        public string? request_token(out string? error_text = null) {
            int status;
            string raw;
            string token = oauth.request_token(request_token_url, out raw, out status);
            if (token == null) {
                if (status == 400) {
                    error_text = get_error_text(raw);
                } else {
                    stdout.printf("%d %s\n", status, raw);
                }
                return null;
            }
            return oauth_token_url + token;
        }

        public bool access_token(string code, out string? error_text = null) {
            int status;
            string raw;
            bool result = oauth.access_token(access_token_url, code, out raw, out status);
            if (result == true) {
                oauth_token        = oauth.get_token();
                oauth_token_secret = oauth.get_token_secret();
                if (dump) {
                    debug("oauth_token:        %s", oauth_token);
                    debug("oauth_token_secret: %s", oauth_token_secret);
                }
            } else {
                if (status == 400) {
                    error_text = get_error_text(raw);
                } else {
                    stdout.printf("%d %s\n", status, raw);
                }
            }
            return result;
        }

        public Data? add(
            string content,
            string qualifier,
            string? limited_to     = null,
            string? no_comments    = null,
            string? lang           = null,
            out string? error_text = null) {

            var query = new HashTable<string,string>(str_hash, str_equal);

            query.replace("content",   content);
            query.replace("qualifier", qualifier);

            if (limited_to != null)
                query.replace("limited_to", limited_to);

            if (no_comments != null)
                query.replace("no_comments", no_comments);

            if (lang != null)
                query.replace("lang", lang);

            int status;

            var raw = oauth.request(api + "Timeline/plurkAdd", query, out status);

            if (status != 200) {
                if (status == 400) {
                    error_text = get_error_text(raw);
                } else {
                    stdout.printf("%d %s\n", status, raw);
                }
                return null;
            }

            return new Data(raw);
        }

        private static string get_error_text(string raw) {
            var json = new JsonSoup.buffer(raw);
            return json.object("error_text").get_string();
        }

        public bool get_unread_count(out HashTable<string,int> unread, out string? error_text = null) {
            int status;
            var raw = oauth.request(api + "Polling/getUnreadCount", null, out status);

            unread = new HashTable<string,int>(str_hash, str_equal);

            if (status != 200) {
                if (status == 400) {
                    error_text = get_error_text(raw);
                } else {
                    stdout.printf("%d %s\n", status, raw);
                }
                return false;
            }

            var json = new JsonSoup.buffer(raw);

            if (json.reset().object("favorite").is_int())
                unread.replace("favorite", (int) json.get_int());

            if (json.reset().object("all").is_int())
                unread.replace("all", (int) json.get_int());

            if (json.reset().object("my").is_int())
                unread.replace("my", (int) json.get_int());

            if (json.reset().object("responded").is_int())
                unread.replace("responded", (int) json.get_int());

            if (json.reset().object("private").is_int())
                unread.replace("private", (int) json.get_int());

            return true;
        }

        public User? get_own_profile(
            out string? error_text = null) {
            User info;
            int status;
            var raw = oauth.request(api + "Profile/getOwnProfile", null, out status);

            if (status != 200) {
                if (status == 400) {
                    error_text = get_error_text(raw);
                } else {
                    stdout.printf("%d %s\n", status, raw);
                }
                return null;
            }

            return new User(raw);
        }
    }

    public class Data : Object {
        private List<int> favorers = null;
        private List<int> replurkers = null;
        private HashTable<string, string> data = null;

        public Data(string raw) {
            favorers = new List<int>();
            replurkers = new List<int>();
            data = new HashTable<string,string>(str_hash, str_equal);
            var json = new JsonSoup.buffer(raw);
            deal_with_json(json, data);
        }

        internal Data.with_json(JsonSoup json) {
            favorers = new List<int>();
            replurkers = new List<int>();
            data = new HashTable<string,string>(str_hash, str_equal);
            deal_with_json(json, data);
        }

        public string lookup(string key) {
            return data.lookup(key);
        }

        private void deal_with_json(JsonSoup json, HashTable<string,string> info) {
            if (json.reset().object("replurkers_count").is_int()) {
                info.replace("replurkers_count", json.get_int().to_string());
            }
            if (json.reset().object("replurkable").is_bool()) {
                info.replace("replurkable", json.get_bool().to_string());
            }
            if (json.reset().object("favorite_count").is_int()) {
                info.replace("favorite_count", json.get_int().to_string());
            }
            if (json.reset().object("is_unread").is_int()) {
                info.replace("is_unread", json.get_int().to_string());
            }
            if (json.reset().object("content").is_string()) {
                info.replace("content", json.get_string());
            }
            if (json.reset().object("user_id").is_int()) {
                info.replace("user_id", json.get_int().to_string());
            }
            if (json.reset().object("plurk_type").is_int()) {
                info.replace("plurk_type", json.get_int().to_string());
            }
            if (json.reset().object("limited_to").is_string()) {
                info.replace("limited_to", json.get_string());
            }
            if (json.reset().object("replurked").is_bool()) {
                info.replace("replurked", json.get_bool().to_string());
            }
            if (json.reset().object("longitude").is_double()) {
                info.replace("longitude", json.get_double().to_string());
            }
            if (json.reset().object("latitude").is_double()) {
                info.replace("latitude", json.get_double().to_string());
            }
            if (json.reset().object("favorers").is_array()) {
                for (int i = 0; i < json.length(); i++) {
                    favorers.append((int) json.array(i).get_int());
                    json.parent();
                }
            }
            if (json.reset().object("qualifier_translated").is_string()) {
                info.replace("qualifier_translated", json.get_string());
            }
            if (json.reset().object("owner_id").is_int()) {
                info.replace("owner_id", json.get_int().to_string());
            }
            if (json.reset().object("responses_seen").is_int()) {
                info.replace("responses_seen", json.get_int().to_string());
            }
            if (json.reset().object("qualifier").is_string()) {
                info.replace("qualifier", json.get_string());
            }
            if (json.reset().object("plurk_id").is_int()) {
                info.replace("plurk_id", json.get_int().to_string());
            }
            if (json.reset().object("response_count").is_int()) {
                info.replace("response_count", json.get_int().to_string());
            }
            if (json.reset().object("no_comments").is_int()) {
                info.replace("no_comments", json.get_int().to_string());
            }
            if (json.reset().object("posted").is_string()) {
                info.replace("posted", json.get_string());
            }
            if (json.reset().object("lang").is_string()) {
                info.replace("lang", json.get_string());
            }
            if (json.reset().object("content_raw").is_string()) {
                info.replace("content_raw", json.get_string());
            }
            if (json.reset().object("replurkers").is_array()) {
                for (int i = 0; i < json.length(); i++) {
                    replurkers.append((int) json.array(i).get_int());
                    json.parent();
                }
            }
            if (json.reset().object("favorite").is_bool()) {
                info.replace("favorite", json.get_bool().to_string());
            }
        }

        public List<int> get_favorers() {
            return favorers.copy();
        }

        public List<int> get_replurkers() {
            return replurkers.copy();
        }
    }

    public class User : Object {
        private HashTable<string, User>   users  = null;
        private HashTable<string, Data>   plurks = null;
        private HashTable<string, string> info   = null;

        public User(string raw) {
            info = new HashTable<string,string>(str_hash, str_equal);
            var json = new JsonSoup.buffer(raw);
            deal_with_json(json);
        }

        private User.with_user_info(string uid, JsonSoup json) {
            info = new HashTable<string,string>(str_hash, str_equal);
            info.replace("uid", uid);
            deal_with_user_info(json, info);
        }

        private void deal_with_json(JsonSoup json) {
            if (json.reset().object("fans_count").is_int())
                info.replace("fans_count", json.get_int().to_string());

            if (json.reset().object("alerts_count").is_int())
                info.replace("alerts_count", json.get_int().to_string());

            if (json.reset().object("friends_count").is_int())
                info.replace("friends_count", json.get_int().to_string());

            if (json.reset().object("privacy").is_string())
                info.replace("privacy", json.get_string());

            if (json.reset().object("has_read_permission").is_bool())
                info.replace("has_read_permission", json.get_bool().to_string());

            if (json.reset().object("unread_count").is_int())
                info.replace("unread_count", json.get_int().to_string());

            if (json.reset().object("user_info").is_object()) {
                var branch = json.branch();
                deal_with_user_info(branch, info);
            }

            if (json.reset().object("plurks_users").is_object()) {
                users = new HashTable<string, User>(str_hash, str_equal);
                foreach (unowned string uid in json.members()) {
                    var branch = json.object(uid).branch();
                    var user   = new User.with_user_info(uid, branch);
                    users.replace(uid, user);
                    json.parent();
                }
            }

            if (json.reset().object("plurks").is_array()) {
                plurks = new HashTable<string, Data>(str_hash, str_equal);
                for (int i = 0; i < json.length(); i++) {
                    var branch = json.array(i).branch();
                    var plurk  = new Data.with_json(branch);
                    plurks.replace(plurk.lookup("plurk_id"), plurk);
                    json.parent();
                }
            }
        }

        private static void deal_with_user_info(JsonSoup json, HashTable<string,string> info) {
            if (json.reset().object("verified_account").is_bool()) {
                info.replace("verified_account", json.get_bool().to_string());
            }
            if (json.reset().object("bday_privacy").is_int()) {
                info.replace("bday_privacy", json.get_int().to_string());
            }
            if (json.reset().object("default_lang").is_string()) {
                info.replace("default_lang", json.get_string());
            }
            if (json.reset().object("display_name").is_string()) {
                info.replace("display_name", json.get_string());
            }
            if (json.reset().object("uid").is_int()) {
                info.replace("uid", json.get_int().to_string());
            }
            if (json.reset().object("relationship").is_string()) {
                info.replace("relationship", json.get_string());
            }
            if (json.reset().object("dateformat").is_int()) {
                info.replace("dateformat", json.get_int().to_string());
            }
            if (json.reset().object("nick_name").is_string()) {
                info.replace("nick_name", json.get_string());
            }
            if (json.reset().object("has_profile_image").is_int()) {
                info.replace("has_profile_image", json.get_int().to_string());
            }
            if (json.reset().object("location").is_string()) {
                info.replace("location", json.get_string());
            }
            if (json.reset().object("avatar").is_int()) {
                info.replace("avatar", json.get_int().to_string());
            }
            if (json.reset().object("is_premium").is_bool()) {
                info.replace("is_premium", json.get_bool().to_string());
            }
            if (json.reset().object("date_of_birth").is_string()) {
                info.replace("date_of_birth", json.get_string());
            }
            if (json.reset().object("email_confirmed").is_bool()) {
                info.replace("email_confirmed", json.get_bool().to_string());
            }
            if (json.reset().object("full_name").is_string()) {
                info.replace("full_name", json.get_string());
            }
            if (json.reset().object("gender").is_int()) {
                info.replace("gender", json.get_int().to_string());
            }
            if (json.reset().object("name_color").is_string()) {
                info.replace("name_color", json.get_string());
            }
            if (json.reset().object("timezone").is_string()) {
                info.replace("timezone", json.get_string());
            }
            if (json.reset().object("recruited").is_int()) {
                info.replace("recruited", json.get_int().to_string());
            }
            if (json.reset().object("id").is_int()) {
                info.replace("id", json.get_int().to_string());
            }
            if (json.reset().object("karma").is_double()) {
                info.replace("karma", json.get_double().to_string());
            }
        }

        public string lookup(string key) {
            return info.lookup(key);
        }

        public List<unowned User>? get_users() {
            return users.get_values();
        }

        public User? get_user(string id) {
            return users.lookup(id);
        }

        public List<unowned Data>? get_plurks() {
            return plurks.get_values();
        }

        public Data? get_plurk(string id) {
            return plurks.lookup(id);
        }
    }
}
/* -*- coding: utf-8; indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- */
/* vim:set fileencodings=utf-8 tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
